package com.fa4j.common.web.controller;


import com.fa4j.common.base.exception.*;
import com.fa4j.common.web.model.CommonWebConst;
import com.fa4j.common.web.model.ResponseVO;
import com.fa4j.common.web.util.ContextUtil;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.MessageSource;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ProblemDetail;
import org.springframework.http.ResponseEntity;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

@Slf4j
@Order(100)
@RestControllerAdvice
@RequiredArgsConstructor
public class ErrorControllerAdvice extends ResponseEntityExceptionHandler {

    private final static List<ErrorType> IGNORE_ERROR_TYPES = List.of(
            CommonRequestError.UNAUTHORIZED, CommonRequestError.FORBIDDEN
    );

    private final MessageSource messageSource;


    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException e,
                                                                  HttpHeaders headers, HttpStatusCode status, WebRequest request) {
        Type paramType = e.getParameter().getGenericParameterType();
        List<String> errors = new ArrayList<>();
        for (FieldError fieldError : e.getFieldErrors()) {
            String fieldName = fieldError.getField();
            Field field = ReflectionUtils.findField((Class<?>) paramType, fieldError.getField());
            if (field != null) {
                Schema schema = AnnotatedElementUtils.findMergedAnnotation(field, Schema.class);
                if (schema != null && !ObjectUtils.isEmpty(schema.description())) {
                    fieldName = schema.description();
                }
            }
            errors.add(fieldName + fieldError.getDefaultMessage());
        }
        RequestException exception = new RequestException(CommonRequestError.BAD_REQUEST,
                String.join(",", errors), null, e);
        return toResponseEntity(e, exception, request);
    }

    @ExceptionHandler(BaseException.class)
    public ResponseEntity<Object> commonExceptionHandler(BaseException e, WebRequest request) {
        return toResponseEntity(e, e, request);
    }

    @ExceptionHandler(Throwable.class)
    public ResponseEntity<Object> error500Handler(Throwable e, WebRequest request) {

        return toResponseEntity(e, new ServerException(CommonServerError.INTERNAL_SERVER_ERROR.getErrMsg(), e), request);
    }

    protected ResponseEntity<Object> toResponseEntity(Throwable e, BaseException exception, WebRequest request) {
        ResponseVO<Object> result = ResponseVO.error(exception.getErrorType().getErrCode(), exception.getMessage());
        String errorMsg = formatErrMsg(exception);

        if (ContextUtil.get(CommonWebConst.PRINT_LOG_FLAG) == null) {
            if (e instanceof RequestException requestException) {
                if (!IGNORE_ERROR_TYPES.contains(requestException.getErrorType())) {
                    log.warn("请求{}异常:{}", request, errorMsg);
                }
            } else if (e instanceof BusinessException) {
                log.warn("请求{}异常", request, e);
            } else {
                log.error("请求{}异常", request, e);
            }
        }

        result.setErrMsg(errorMsg);
        if (exception instanceof BusinessException businessException) {
            result.setData(businessException.getErrData());
        }
        return ResponseEntity.ok(result);
    }

    private String formatErrMsg(BaseException exception) {
        List<Object> argList = new ArrayList<>();
        ErrorType errorType = exception.getErrorType();
        if (exception.getArgs() != null) {
            for (Object arg : exception.getArgs()) {
                if (arg instanceof String argStr && argStr.startsWith("{") && argStr.endsWith("}")) {
                    argList.add(messageSource.getMessage(argStr, null, argStr, Locale.getDefault()));
                } else {
                    argList.add(arg);
                }
            }
        }
        if (exception instanceof ServerException) {
            return CommonServerError.INTERNAL_SERVER_ERROR.getErrMsg();
        }
        String errMsgKey = errorType.getErrMsgKey();
        if (exception.getErrMsg() != null) {
            if (exception.getErrMsg().startsWith("{") && exception.getErrMsg().startsWith("}")) {
                errMsgKey = exception.getErrMsg().substring(1, exception.getErrMsg().length() - 1);
            } else {
                return MessageFormat.format(exception.getErrMsg(), argList.toArray());
            }
        }
        String defaultMsg = MessageFormat.format(exception.getErrorType().getErrMsg(), argList.toArray());
        return messageSource.getMessage(errMsgKey, argList.toArray(), defaultMsg, Locale.getDefault());
    }

    @Override
    protected ResponseEntity<Object> createResponseEntity(Object body, HttpHeaders headers, HttpStatusCode statusCode,
                                                          WebRequest request) {
        if (ContextUtil.get(CommonWebConst.PRINT_LOG_FLAG) == null) {
            log.error("请求{}异常:{}", request, body);
        }
        ResponseVO<?> result = new ResponseVO<>();
        result.setSuccess(false);
        if (body instanceof ProblemDetail detail) {
            result.setErrCode(detail.getStatus());
            result.setErrMsg(detail.getDetail());
        }
        return ResponseEntity.ok(result);
    }
}